use crate::PAGE_SIZE_BITS;

pub const PA_WIDTH_SV39: usize = 56;
pub const VA_WIDTH_SV39: usize = 39;

pub const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - PAGE_SIZE_BITS;
pub const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS;

pub const VA_INDEX_WIDTH: usize = (VA_WIDTH_SV39 - PAGE_SIZE_BITS) / 3;

///////////////////////////////////////////////////////////

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PhysAddr(pub usize);

impl From<PhysAddr> for usize {
    fn from(value: PhysAddr) -> Self {
        value.0
    }
}

impl From<usize> for PhysAddr {
    fn from(value: usize) -> Self {
        Self(
            value & ((1 << PA_WIDTH_SV39) - 1)
        ) 
    }
}

impl PhysAddr {
    pub fn num_and_offset(ppn: PhysPageNum, offset: usize) -> Self {
        ((ppn.0 << PAGE_SIZE_BITS) + offset).into()
    }
}

///////////////////////////////////////////////////////////

#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct PhysPageNum(pub usize);

impl From<usize> for PhysPageNum {
    fn from(value: usize) -> Self {
        Self (
            value & ((1 << PPN_WIDTH_SV39) - 1)
        )
    }
}

impl From<PhysPageNum> for usize {
    fn from(value: PhysPageNum) -> Self {
        value.0
    }
}

///////////////////////////////////////////////////////////

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VirtAddr(pub usize);

impl From<VirtAddr> for usize {
    fn from(value: VirtAddr) -> Self {
        value.0
    }
}

impl From<usize> for VirtAddr {
    fn from(value: usize) -> Self {
        Self(
            value & ((1 << VA_WIDTH_SV39) - 1)
        ) 
    }
}

const VA_INDEX_MASK: usize = (1 << VA_INDEX_WIDTH) - 1;

impl VirtAddr {
    pub fn num_and_offset(vpn: VirtPageNum, offset: usize) -> Self {
        ((vpn.0 << PAGE_SIZE_BITS) + offset).into()
    }

    pub fn divide(&self) -> (VirtPageNum, usize) {
        let value: usize = self.0;
        let offset = value & ((1 << PAGE_SIZE_BITS) - 1);
        let vpn = (value >> PAGE_SIZE_BITS).into();
        (vpn, offset)
    }

    pub fn parse(&self) -> (usize, usize, usize, usize) {
        let (vpn, offset) = self.divide();
        let (index0, index1, index2) = vpn.parse();
        (index0, index1, index2, offset)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VirtPageNum(pub usize);

impl VirtPageNum {
    pub fn parse(&self) -> (usize, usize, usize) {
        let value: usize = self.0;
        let index2 = 
            (value >> (0 * VA_INDEX_WIDTH)) & VA_INDEX_MASK;
        let index1 = 
            (value >> (1 * VA_INDEX_WIDTH)) & VA_INDEX_MASK;
        let index0 = 
            (value >> (2 * VA_INDEX_WIDTH)) & VA_INDEX_MASK;

        (index0, index1, index2)
    }
}

impl From<usize> for VirtPageNum {
    fn from(value: usize) -> Self {
        Self (
            value & ((1 << VPN_WIDTH_SV39) - 1)
        )
    }
}

impl From<VirtPageNum> for usize {
    fn from(value: VirtPageNum) -> Self {
        value.0
    }
}